001 /* 002 * LSFResourceStrategy.java 003 * 004 * Created on April 1, 2003, 3:30 PM 005 * 006 * This file is part of the STAR Scheduler. 007 * Copyright (c) 2002-2003 STAR Collaboration - Brookhaven National Laboratory 008 * 009 * STAR Scheduler is free software; you can redistribute it and/or modify 010 * it under the terms of the GNU General Public License as published by 011 * the Free Software Foundation; either version 2 of the License, or 012 * (at your option) any later version. 013 * 014 * STAR Scheduler is distributed in the hope that it will be useful, 015 * but WITHOUT ANY WARRANTY; without even the implied warranty of 016 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 017 * GNU General Public License for more details. 018 * 019 * You should have received a copy of the GNU General Public License 020 * along with STAR Scheduler; if not, write to the Free Software 021 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 022 */ 023 package gov.bnl.star.offline.scheduler.lsf; 024 025 import gov.bnl.star.offline.scheduler.*; 026 import gov.bnl.star.offline.scheduler.catalog.PhysicalFile; 027 028 import java.util.Hashtable; 029 import java.util.Iterator; 030 import java.util.Set; 031 import java.util.logging.Logger; 032 import java.util.regex.Matcher; 033 import java.util.regex.Pattern; 034 035 036 /** Encapsulate the resource usage parameter (-R) for an LSF farm. Essentially, 037 * its a placeholder for the prepareResourceUsageSwitch() which will calculate the 038 * appriate string. The administrator should be able to create different 039 * subclasses for each farm as needed. 040 * <p> 041 * This class provides a scheleton for a typical resource usage for files residing 042 * on different NFS servers. It was designed to fit STAR needs at two sites. In case 043 * it doesn't fit the needs, the class can be subclassed to define new behaviour. 044 * <p> 045 * The resource switch is prepared in this way: 046 * Takes the list of input files and iterates over them.<br> 047 * Checks if each file needs a resource (<CODE>resourceRequired()</CODE>).<br> 048 * To determine that, checks if the file is on NFS, and looks for the typical data 049 * vault prefix (<CODE>getDataVaultPrefix</CODE>). For example, "/star/dataxx/..."; 050 * it extract the vaultNumber and it substitute it in the resourceSyntax <br> 051 * Increases the count of the resource (<CODE>increaseResourceUsage</CODE>)<br> 052 * Prepares the switch (<CODE>prepareSwitch</CODE>) 053 * <p> 054 * The resource value for each resource will the be given by the following formula: <br> 055 * <code>min (base+nCount*fileFactor, max)</code> 056 * <p> 057 * The subclass need to implement the abstract methods, which defines things that 058 * always change between different farms. 059 * <p> 060 * TODO Further work should be done to be able to configure the ResourceStrategy 061 * depending on the farm on which the job will be dispatched. This should be 062 * included in the GRID dispatchers. 063 ** 064 * @author Gabriele Carcassi, Jerome Lauret 065 * @version July 24, 2003 066 */ 067 public class LSFResourceStrategy { 068 static private Logger log = Logger.getLogger(LSFResourceStrategy.class.getName()); 069 070 /** Base value for the resource value formula. */ 071 protected int base = 0; 072 073 /** Returns the base value for the resource value formula. */ 074 public int getBase() { 075 return base; 076 } 077 078 /** Changes the base value for the resource value formula. */ 079 public void setBase(int base) { 080 this.base = base; 081 } 082 083 /** FileFactor value for the resource value formula. */ 084 protected int fileFactor = 2; 085 086 /** Returns the fileFactor value for the resource value formula. */ 087 public int getFileFactor() { 088 return fileFactor; 089 } 090 091 /** Changes the fileFactor value for the resource value formula. */ 092 public void setFileFactor(int fileFactor) { 093 this.fileFactor = fileFactor; 094 } 095 096 /** Max parameter for the standard increaseResourceUsage implementation. */ 097 protected int max = 50; 098 099 /** Returns the max value for the resource value formula. */ 100 public int getMax() { 101 return max; 102 } 103 104 /** Changes the max value for the resource value formula. */ 105 public void setMax(int max) { 106 this.max = max; 107 } 108 109 private Hashtable resources; 110 111 private String dataVaultPrefix; 112 113 /** Returns the prefix with which a file on NFS identifies on which file server or 114 * data vault is residing. For example, "/star/data07/muDST/..." represents a file 115 * on data vault "07" (that is the data vault number or id), therefore the prefix is "/star/data". 116 * @return The prefix that identifies data vaults on NFS. 117 */ 118 public String getDataVaultPrefix() { 119 return dataVaultPrefix; 120 } 121 122 /** Changes the prefix of a NFS file. The resource identifies the data vault number 123 * by looking at a prefix in the file name. For example "/star/data07/minbias/..." 124 * would have "/star/data" as a prefix. 125 */ 126 public void setDataVaultPrefix(String dataVaultPrefix) { 127 this.dataVaultPrefix = dataVaultPrefix; 128 } 129 130 private String resourceNameSyntax; 131 132 /** Returns the syntax to be used to convert a data vault number to the LSF resource. 133 */ 134 public String getResourceNameSyntax() { 135 return resourceNameSyntax; 136 } 137 138 /** Changes the syntax to be used to convert a data vault number to the LSF resource. 139 * It can be any string, and it should contain the $vaultNumber variable, which will 140 * be substituted with the particular vault number. For example, to have something 141 * like "dv7io", you should have "dv$vaultNumberio". 142 */ 143 public void setResourceNameSyntax(String resourceNameSyntax) { 144 this.resourceNameSyntax = resourceNameSyntax; 145 } 146 147 private boolean trimVaultNumberLeadingZeros; 148 149 /** Returns true if the leading zeros of a vault number will be removed when 150 * creating the corresponding LSF resource. 151 */ 152 public boolean isTrimVaultNumberLeadingZeros() { 153 return trimVaultNumberLeadingZeros; 154 } 155 156 /** Set to true if the leading zeros of a vault number will be removed when 157 * creating the corresponding LSF resource. Basically, 07 will be changed to 7. 158 */ 159 public void setTrimVaultNumberLeadingZeros(boolean trimVaultNumberLeadingZeros) { 160 this.trimVaultNumberLeadingZeros = trimVaultNumberLeadingZeros; 161 } 162 163 /** Given the vault id, returns the LSF resource name to be used in the -R 164 * parameter. 165 * @param vaultNumber the vault id, as identified in <CODE>getDataVaultPrefix</CODE> 166 * @return the LSF resource name for the vault 167 */ 168 protected String prepareResourceName(String vaultNumber) { 169 if ((removeCharactersFromVaultNumber) && (!vaultNumber.matches("[a-zA-Z]+"))) { 170 Pattern pattern = Pattern.compile("[^a-zA-Z]+"); 171 Matcher matcher = pattern.matcher(vaultNumber); 172 StringBuffer buf = new StringBuffer(vaultNumber.length()); 173 StringBuffer vaultNumberBuf = new StringBuffer(vaultNumber); 174 while (matcher.find()) { 175 buf.append(vaultNumberBuf.subSequence(matcher.start(), matcher.end())); 176 } 177 vaultNumber = buf.toString(); 178 } 179 180 if (trimVaultNumberLeadingZeros) { 181 while (vaultNumber.startsWith("0") && (vaultNumber.length() != 1)) { 182 vaultNumber = vaultNumber.substring(1); 183 } 184 } 185 186 if (trimVaultNumberDecimal) { 187 int dec = vaultNumber.indexOf('.'); 188 if (dec != -1) vaultNumber = vaultNumber.substring(0, dec); 189 } 190 191 String name = resourceNameSyntax.replaceAll("\\$vaultNumber", vaultNumber); 192 193 return name; 194 } 195 196 private String switchSyntax; 197 198 /** Holds value of property trimVaultNumberDecimal. */ 199 private boolean trimVaultNumberDecimal; 200 201 /** Holds value of property removeCharactersFromVaultNumber. */ 202 private boolean removeCharactersFromVaultNumber; 203 204 /** Returns the syntax that will be used to prepare the final switch 205 */ 206 public String getSwitchSyntax() { 207 return switchSyntax; 208 } 209 210 /** Changes the syntax that will be used to prepare the final switch. 211 * You can use the $definedNames, $nameEqualValueCommaSeparated 212 * or $nameEqualValueColumnSeparated variables in your switch. 213 * For example, the following switch: 214 * "\"select[$definedNames] rusage[$nameEqualValueCommaSeparated]\"" 215 * will produce something like: "select[defined(01)] rusage[01=50]" 216 */ 217 public void setSwitchSyntax(String switchSyntax) { 218 this.switchSyntax = switchSyntax; 219 } 220 221 /** Prepares the -R parameters to give to LSF at the end of the resource 222 * distribution. It will use <CODE>definedNames()</CODE>, 223 * <CODE>nameEqualValueCommaSeparated()</CODE> or 224 * <CODE>nameEqualValueColumnSeparated()</CODE> to prepare it more easily. 225 * @return the -R parameter for the current job 226 */ 227 protected String prepareSwitch() { 228 if (determineResourceNotUsed()) { 229 return null; 230 } 231 232 String fullSwitch = switchSyntax.replaceAll("\\$definedNames", definedNames()); 233 fullSwitch = fullSwitch.replaceAll("\\$nameEqualValueCommaSeparated", nameEqualValueCommaSeparated()); 234 fullSwitch = fullSwitch.replaceAll("\\$nameEqualValueColumnSeparated", nameEqualValueColumnSeparated()); 235 236 return fullSwitch; 237 } 238 239 /** Increase the resource usage as part of the standard implementation. The standard 240 * formula starts counting from <CODE>base</CODE>, adds <CODE>fileFactor</CODE> for 241 * each usage until <CODE>max</CODE> is reached. In pseudo-code: 242 * <CODE>if (nRes == 0) nRes = base; 243 * nRes += nRes; 244 * if (nRes > max) nRes = max; 245 * </CODE> 246 * <p> 247 * One can modify the three protected parameters (base, fileFactor, max) to tune 248 * the usage. 249 * @param resource the resource for which to increase the counting. 250 */ 251 protected void increaseResourceUsage(String resource) { 252 increaseResourceUptoLimit(resource, base, fileFactor, max); 253 } 254 255 /** Addes a new resource, and initializes the value to <CODE>base</CODE>. 256 * @param resName the name of the resource 257 * @param base the initial value for the resource 258 */ 259 protected void addResource(String resName, int base) { 260 if (!resources.containsKey(resName)) { 261 resources.put(resName, new Integer(base)); 262 } else { 263 throw new RuntimeException("Can't add resource " + resName + 264 " because it's already present."); 265 } 266 } 267 268 /** Increases the value associated to the given resource by a given amount. If the 269 * resource doesn't exists, first it will be created and initialized. 270 * @param resName the name of the resource 271 * @param base the initial value if the resource has to be created 272 * @param amount the amount to increase the resource by 273 * @return the new value associated to the resource 274 */ 275 protected int increaseResource(String resName, int base, int amount) { 276 if (!resources.containsKey(resName)) { 277 addResource(resName, base); 278 } 279 280 int returnValue = prepareResourceValue(resName) + amount; 281 Integer newValue = new Integer(returnValue); 282 283 resources.remove(resName); 284 resources.put(resName, newValue); 285 286 return returnValue; 287 } 288 289 /** Increases the value associated to the given resource by a given amount. If the 290 * resource doesn't exists, first it will be created and initialized. If the 291 * increased amount is over the limit, the value will be set to the limit. 292 * @return the new value associated to the resource 293 * @param limit upper limit to which the resource value will be set 294 * @param resName the name of the resource 295 * @param base the initial value if the resource has to be created 296 * @param amount the amount to increase the resource by 297 */ 298 protected int increaseResourceUptoLimit(String resName, int base, 299 int amount, int limit) { 300 if (!resources.containsKey(resName)) { 301 addResource(resName, base); 302 } 303 304 int returnValue = prepareResourceValue(resName) + amount; 305 306 if (returnValue > limit) { 307 return limit; 308 } 309 310 Integer newValue = new Integer(returnValue); 311 312 resources.remove(resName); 313 resources.put(resName, newValue); 314 315 return returnValue; 316 } 317 318 /** Returns the value currently associated to the resource. 319 * @param resName resource name 320 * @return resource value 321 */ 322 protected int prepareResourceValue(String resName) { 323 return ((Integer) resources.get(resName)).intValue(); 324 } 325 326 /** Returns the -R parameter to be used to dispatch the given job. 327 * @param job the job to be dispatched 328 * @return the -R parameter to use 329 */ 330 public String prepareResourceUsageSwitch(Job job) { 331 initResources(); 332 calculateLSFResources(job); 333 334 return prepareSwitch(); 335 } 336 337 /** Initializes the resource table. */ 338 protected void initResources() { 339 resources = new Hashtable(); 340 } 341 342 /** Returns a string already formatted containing names and values comma separated 343 * (i.e. "res1=34,res2=12"). 344 * @return resource name/value pairs comma separated (i.e. "res1=34,res2=12") 345 */ 346 protected String nameEqualValueCommaSeparated() { 347 String rusage = ""; 348 Set resNames = resources.keySet(); 349 Iterator iter = resNames.iterator(); 350 boolean first = true; 351 352 while (iter.hasNext()) { 353 if (!first) { 354 rusage += ","; 355 } 356 357 Object key = iter.next(); 358 rusage += (key + "=" + resources.get(key)); 359 first = false; 360 } 361 362 return rusage; 363 } 364 365 /** Returns a string already formatted containing names and values column 366 * separated (i.e. "res1=34:res2=12"). 367 * @return resource name/value pairs column separated (i.e. "res1=34:res2=12") 368 */ 369 370 protected String nameEqualValueColumnSeparated() { 371 String rusage = ""; 372 Set resNames = resources.keySet(); 373 Iterator iter = resNames.iterator(); 374 boolean first = true; 375 376 while (iter.hasNext()) { 377 if (!first) { 378 rusage += ":"; 379 } 380 381 Object key = iter.next(); 382 rusage += (key + "=" + resources.get(key)); 383 first = false; 384 } 385 386 return rusage; 387 } 388 389 390 391 /** A list of names, comma separated, of all the resources used. 392 * @return (i.e. "res1,res2") 393 */ 394 protected String definedNames() { 395 String defined = ""; 396 Set resNames = resources.keySet(); 397 Iterator iter = resNames.iterator(); 398 boolean first = true; 399 400 while (iter.hasNext()) { 401 if (!first) { 402 defined += " && "; 403 } 404 405 Object key = iter.next(); 406 defined += ("defined(" + key + ")"); 407 first = false; 408 } 409 410 return defined; 411 } 412 413 /** Filles the resource table with all the resources. It basically iterates over the 414 * input files and calls <CODE>increaseResourceUsage</CODE> for each file that map 415 * to a resource. 416 * @param job the job 417 */ 418 protected void calculateLSFResources(Job job) { 419 log.fine("Calculating LSF resources"); 420 for (int i = 0; i < job.getInput().size(); i++) { 421 PhysicalFile file = (PhysicalFile) job.getInput().get(i); 422 String res = resourceRequired(file); 423 424 if (res != null) { 425 increaseResourceUsage(res); 426 } 427 } 428 } 429 430 /** Determines which resource the job might need to access the file. 431 * @param file input file the job will be using 432 * @return the resource needed 433 */ 434 protected String resourceRequired(PhysicalFile file) { 435 if ("NFS".equals(file.getStorage())) { 436 log.finest("Found NFS file: " + file); 437 438 String path = file.getPath(); 439 440 if (path.startsWith(getDataVaultPrefix())) { 441 int bar = path.indexOf('/', getDataVaultPrefix().length()); 442 String serverNumber = path.substring(getDataVaultPrefix() 443 .length(), bar); 444 445 return prepareResourceName(serverNumber); 446 } 447 } 448 449 return null; 450 } 451 452 /** Determines whether any resources at all have been assigned. 453 * @return true if any resource is used. 454 */ 455 protected boolean determineResourceNotUsed() { 456 return resources.isEmpty(); 457 } 458 459 /** Getter for property trimVaultNumberDecimal. 460 * @return Value of property trimVaultNumberDecimal. 461 * 462 */ 463 public boolean isTrimVaultNumberDecimal() { 464 return this.trimVaultNumberDecimal; 465 } 466 467 /** Setter for property trimVaultNumberDecimal. 468 * @param trimVaultNumberDecimal New value of property trimVaultNumberDecimal. 469 * 470 */ 471 public void setTrimVaultNumberDecimal(boolean trimVaultNumberDecimal) { 472 this.trimVaultNumberDecimal = trimVaultNumberDecimal; 473 } 474 475 /** If true removes all the characters from the vault number. 476 * @return Value of property removeCharactersFromVaultNumber. 477 * 478 */ 479 public boolean isRemoveCharactersFromVaultNumber() { 480 return this.removeCharactersFromVaultNumber; 481 } 482 483 /** Set to true removes all the characters from the vault number 484 * @param removeCharactersFromVaultNumber New value of property removeCharactersFromVaultNumber. 485 * 486 */ 487 public void setRemoveCharactersFromVaultNumber(boolean removeCharactersFromVaultNumber) { 488 this.removeCharactersFromVaultNumber = removeCharactersFromVaultNumber; 489 } 490 491 }